package filesrv

import (
	"net/http"
	"cloud_worm/util/proto/ujson"
	"cloud_worm/router/config"
	"cloud_worm/util/uhttp"
	"github.com/golang/glog"
	"fmt"
	"encoding/json"
	"strings"
	"io/ioutil"
	"path/filepath"
	"os"
	"io"
	"cloud_worm/util/ufile"
	"cloud_worm/util/umodels/mysqldb/dbmanager"
)

const (
	wormFileDir = "worm/worm"
	weaponFileDir = "worm/weapons"
)

func GrayRouterGet(rw http.ResponseWriter, req *http.Request)  {
	req.ParseForm()

	config.ConfInst.GrayConfig.RLock()
	grayConfig := ujson.GrayConfig{
		LocalIP: 	config.ConfInst.LocalAddr,
		Version:    config.ConfInst.GrayConfig.Version,
		TargetType: config.ConfInst.GrayConfig.TargetType,
		Target:     config.ConfInst.GrayConfig.Target,
	}
	config.ConfInst.GrayConfig.RUnlock()

	resp := uhttp.ResponData{Code: uhttp.RespCodeOk, Msg: "OK", Data: grayConfig}
	resp.SendResponse(rw)
	return
}

func GrayRouterSet(rw http.ResponseWriter, req *http.Request)  {
	req.ParseForm()

	input := new(ujson.GrayConfig)
	body, err := ioutil.ReadAll(req.Body)
	if err != nil {
		glog.Error(fmt.Sprintf("request body is error: %s", err.Error()))
		resp := uhttp.ResponData{Code: uhttp.RespCodeReqBodyErr, Msg: "request body is error"}
		resp.SendResponse(rw)
		return
	}
	if err = json.Unmarshal(body, input); err != nil {
		glog.Error("body json Unmarshal failed: ", err.Error())
		resp := uhttp.ResponData{Code: uhttp.RespCodeJsonErr, Msg: "body json Unmarshal failed"}
		resp.SendResponse(rw)
		return
	}

	config.ConfInst.GrayConfig.Lock()
	config.ConfInst.GrayConfig.Version = input.Version
	config.ConfInst.GrayConfig.TargetType = input.TargetType
	config.ConfInst.GrayConfig.Target = input.Target
	config.ConfInst.GrayConfig.TargetSet = make(map[string]struct{})
	for _, item := range strings.Split(input.Target, ",") {
		l := strings.TrimSpace(item)
		if len(l) <= 0 {
			continue
		}

		config.ConfInst.GrayConfig.TargetSet[l] = struct{}{}
	}
	config.ConfInst.GrayConfig.Unlock()

	resp := uhttp.ResponData{Code: uhttp.RespCodeOk, Msg: "OK", Data: nil}
	resp.SendResponse(rw)
	return
}

/**
 查找agent最大的版本号，最新版本号
 http://www.xxx.com:8080/wolagent/max_ver
 */
func MaxAgentVersion(rw http.ResponseWriter, req *http.Request) {
	req.ParseForm()

	var ignoreTestVersion bool
	// 先灰度匹配
	remoteIP := strings.Split(req.RemoteAddr, ":")[0]

	config.ConfInst.GrayConfig.RLock()									// lock
	if config.ConfInst.GrayConfig.TargetType == ujson.GrayTargetTypeIP {			// ip
		for k, _ := range config.ConfInst.GrayConfig.TargetSet {
			if k == remoteIP {
				fmt.Fprintf(rw, config.ConfInst.GrayConfig.Version)
				config.ConfInst.GrayConfig.RUnlock()					// unlock
				return
			}
		}
		ignoreTestVersion = true
	} else if config.ConfInst.GrayConfig.TargetType == ujson.GrayTargetTypeMask {		// ip段
		for k, _ := range config.ConfInst.GrayConfig.TargetSet {
			if strings.HasPrefix(remoteIP, k) {
				fmt.Fprintf(rw, config.ConfInst.GrayConfig.Version)
				config.ConfInst.GrayConfig.RUnlock()					// unlock
				return
			}
		}
		ignoreTestVersion = true
	} else {
		ignoreTestVersion = false
	}
	config.ConfInst.GrayConfig.RUnlock()

	// 正常逻辑处理
	var platform string
	platforms, found1 := req.Form["platform"]
	if !found1 {
		platform = dbmanager.PlatLinux				// 如果没有输入，则缺省是linux
	} else {
		platform = string(platforms[0])
	}

	dir, err := ioutil.ReadDir(filepath.Join(config.ConfInst.Server.FileBaseDir, wormFileDir))
	if err != nil {
		fmt.Fprintln(rw, "No agent file dir")
		return
	}

	var currVersion string
	for _, item := range dir {
		if item.IsDir() {
			continue
		}

		if ignoreTestVersion == true && strings.Contains(item.Name(), config.ConfInst.GrayConfig.Version) {
			continue
		}

		if strings.HasPrefix(item.Name(), "wolagent") && strings.HasSuffix(item.Name(), fmt.Sprintf(".%s.tar.gz", platform)) {
			tmpStrings := strings.Split(item.Name(), ".")
			if currVersion == "" {
				currVersion = tmpStrings[1]
			} else {
				if currVersion < tmpStrings[1] {
					currVersion = tmpStrings[1]
				}
			}
		}
	}

	if len(currVersion) == 0 {
		fmt.Fprintln(rw, "No agent version info.")
	} else {
		fmt.Fprintf(rw, currVersion)
	}
	return
}

/**
 检查文件上传的白名单
 */
func checkUploadIP(req *http.Request) error {
	if len(config.ConfInst.Server.UploadIPs) == 0 {
		return nil
	}

	elems := strings.Split(req.RemoteAddr, ":")
	if len(elems) != 2 {
		return fmt.Errorf("invalid remote addr: %s", req.RemoteAddr)
	}

	ip := elems[0]
	for _, item := range config.ConfInst.Server.UploadIPs {
		if ip == item {
			return nil
		}
	}

	return fmt.Errorf("invalid request from ip: %s", ip)
}

var urlPrefix string

/**
 文件上传
 http://studygolang.com/articles/2376
 */
func UploadWormFile(rw http.ResponseWriter, req *http.Request) {

	err := checkUploadIP(req)
	if err != nil {
		glog.Warningf("illegality upload file: %s", err.Error())
		responData := uhttp.ResponData{
			Code: uhttp.RespCodeAuthErr,
			Msg:  fmt.Sprintf("upload file auth fail"),
		}
		responData.SendResponse(rw)
		return
	}

	if req.Method == "POST" {
		// 读出文件内容
		fp, handle, err := req.FormFile("file")
		if err != nil {
			glog.Error("read formfile error!!")
			responData := uhttp.ResponData{
				Code: uhttp.RespCodeSystemErr,
				Msg:  fmt.Sprintf("read formfile error!!"),
			}
			responData.SendResponse(rw)
			return
		}
		defer fp.Close()

		// 获取用户传递的md5
		ClientMd5, found1 := req.Form["md5"]
		if !found1 {
			glog.Error("read md5 error!!")
			responData := uhttp.ResponData{
				Code: uhttp.RespCodeSystemErr,
				Msg:  fmt.Sprint("read md5 error!!"),
			}
			responData.SendResponse(rw)
			return
		}

		// 防止跨目录 瞎搞
		if strings.Contains(handle.Filename, "..") {
			glog.Error("file contains illegality")
			responData := uhttp.ResponData{
				Code: uhttp.RespCodeSystemErr,
				Msg:  fmt.Sprint("file contains illegality"),
			}
			responData.SendResponse(rw)
			return
		}

		// 先写入文件，再md5
		_, fileName := filepath.Split(handle.Filename)
		destFileName := filepath.Join(config.ConfInst.Server.FileBaseDir, wormFileDir, fileName)
		os.Remove(destFileName)

		newFp, err := os.OpenFile(destFileName, os.O_WRONLY|os.O_CREATE, 0644)
		if err != nil {
			glog.Errorf("open file '%s' error: %s", destFileName, err.Error())
			responData := uhttp.ResponData{
				Code: uhttp.RespCodeSystemErr,
				Msg:  fmt.Sprintf("open file '%s' error: %s", destFileName, err.Error()),
			}
			responData.SendResponse(rw)
			return
		}
		defer newFp.Close()

		// 拷贝文件内容
		_, err = io.Copy(newFp, fp)
		if err != nil {
			glog.Errorf("copy file error: %s", err.Error())
			responData := uhttp.ResponData{
				Code: uhttp.RespCodeSystemErr,
				Msg:  fmt.Sprintf("copy file error: %s", err.Error()),
			}
			responData.SendResponse(rw)
			return
		}

		// 计算改文件的md5,并比较是否一直
		fileMd5 := ufile.GetFileMd5(destFileName)
		if fileMd5 != strings.ToLower(string(ClientMd5[0])) {
			glog.Error("md5 not same,upload fail")
			responData := uhttp.ResponData{
				Code: uhttp.RespCodeSystemErr,
				Msg:  "md5 not same,upload fail",
			}
			responData.SendResponse(rw)
			return
		}

		if len(urlPrefix) <= 0 {
			if config.ConfInst.TLSCertInfo.KeyFile == "" {
				urlPrefix = fmt.Sprintf("http://%s:%s", config.ConfInst.Server.Domain, config.ConfInst.Server.FilePort)
			} else {
				urlPrefix = fmt.Sprintf("https://%s:%s", config.ConfInst.Server.Domain, config.ConfInst.Server.FilePort)
			}
		}
		responData := uhttp.ResponData{
			Code: uhttp.RespCodeOk,
			Msg:  "ok",
			Data: map[string]interface{}{
				"url": fmt.Sprintf("https://%s:%s/%s", config.ConfInst.Server.Domain,
					config.ConfInst.Server.FilePort, filepath.Join(wormFileDir, fileName))},
		}

		responData.SendResponse(rw)
	} else {
		responData := uhttp.ResponData{Code: uhttp.RespCodeParamErr, Msg: fmt.Sprintf("can not support mothod: %s", req.Method)}
		responData.SendResponse(rw)
	}
	return
}

/**
 文件上传
 http://studygolang.com/articles/2376
 http://lib.csdn.net/article/go/34373
 */
func UploadWeaponFile(rw http.ResponseWriter, req *http.Request) {

	err := checkUploadIP(req)
	if err != nil {
		glog.Warningf("illegality upload file: %s", err.Error())
		responData := uhttp.ResponData{
			Code: uhttp.RespCodeAuthErr,
			Msg:  fmt.Sprintf("upload file auth fail"),
		}
		responData.SendResponse(rw)
		return
	}

	if req.Method == "POST" {
		// 读出文件内容
		fp, handle, err := req.FormFile("file")
		if err != nil {
			glog.Errorf("read formfile error: %s", err.Error())
			responData := uhttp.ResponData{
				Code: uhttp.RespCodeSystemErr,
				Msg:  fmt.Sprintf("read formfile error: %s", err.Error()),
			}
			responData.SendResponse(rw)
			return
		}
		defer fp.Close()

		// 获取用户传递的md5
		ClientMd5, found1 := req.Form["md5"]
		if !found1 {
			glog.Error("read md5 error!!")
			responData := uhttp.ResponData{
				Code: uhttp.RespCodeSystemErr,
				Msg:  fmt.Sprint("read md5 error!!"),
			}
			responData.SendResponse(rw)
			return
		}

		// 防止跨目录 瞎搞
		if strings.Contains(handle.Filename, "..") {
			glog.Error("file contains illegality")
			responData := uhttp.ResponData{
				Code: uhttp.RespCodeSystemErr,
				Msg:  fmt.Sprint("file contains illegality"),
			}
			responData.SendResponse(rw)
			return
		}

		// 先写文件,再比较md5
		_, fileName := filepath.Split(handle.Filename)
		destFileName := filepath.Join(config.ConfInst.Server.FileBaseDir, weaponFileDir, fileName)
		os.Remove(destFileName)

		newFp, err := os.OpenFile(destFileName, os.O_WRONLY|os.O_CREATE, 0644)
		if err != nil {
			glog.Errorf("open file '%s' error: %s", destFileName, err.Error())
			responData := uhttp.ResponData{
				Code: uhttp.RespCodeSystemErr,
				Msg:  fmt.Sprintf("open file '%s' error: %s", destFileName, err.Error()),
			}
			responData.SendResponse(rw)
			return
		}
		defer newFp.Close()

		// 拷贝文件内容
		_, err = io.Copy(newFp, fp)
		if err != nil {
			glog.Errorf("copy file error: %s", err.Error())
			responData := uhttp.ResponData{
				Code: uhttp.RespCodeSystemErr,
				Msg:  fmt.Sprintf("copy file error: %s", err.Error()),
			}
			responData.SendResponse(rw)
			return
		}

		// 计算改文件的md5,并比较是否一直
		fileMd5 := ufile.GetFileMd5(destFileName)
		if fileMd5 != strings.ToLower(string(ClientMd5[0])) {
			glog.Error("md5 not same,upload fail")
			responData := uhttp.ResponData{
				Code: uhttp.RespCodeSystemErr,
				Msg:  "md5 not same,upload fail",
			}
			responData.SendResponse(rw)
			return
		}

		if len(urlPrefix) <= 0 {
			if config.ConfInst.TLSCertInfo.KeyFile == "" {
				urlPrefix = fmt.Sprintf("http://%s:%s", config.ConfInst.Server.Domain, config.ConfInst.Server.FilePort)
			} else {
				urlPrefix = fmt.Sprintf("https://%s:%s", config.ConfInst.Server.Domain, config.ConfInst.Server.FilePort)
			}
		}
		responData := uhttp.ResponData{
			Code: uhttp.RespCodeOk,
			Msg:  "ok",
			Data: map[string]interface{}{
				"url": fmt.Sprintf("%s/%s", urlPrefix, filepath.Join(weaponFileDir, fileName))},
		}

		responData.SendResponse(rw)
	} else {
		responData := uhttp.ResponData{Code: uhttp.RespCodeParamErr, Msg: fmt.Sprintf("can not support mothod: %s", req.Method)}
		responData.SendResponse(rw)
	}
	return
}

